using UnityEngine;

// 定义一个名为 Graph 的 MonoBehaviour 类，用于在三维空间中动态绘制数学函数的点阵图形
public class Graph : MonoBehaviour
{

	// 序列化字段，在 Inspector 中可拖拽赋值，表示用于表示单个数据点的 Prefab（通常是带有 Transform 的小球或标记）
	[SerializeField]
	Transform pointPrefab;

	// 序列化且可调节的字段，表示网格的分辨率（点阵的密度），范围限制在 10 到 100 之间，默认 10
	[SerializeField, Range(10, 100)]
	int resolution = 10;

	// 序列化字段，表示当前要绘制的数学函数类型，来自 FunctionLibrary 的枚举
	[SerializeField]
	FunctionLibrary.FunctionName function;

	// 自定义枚举：定义函数切换时的过渡模式，可以是 Cycle（循环切换）或 Random（随机切换）
	public enum TransitionMode { Cycle, Random }

	// 序列化字段，表示当前使用的函数切换模式
	[SerializeField]
	TransitionMode transitionMode;

	// 序列化且最小值为 0 的字段，表示每个数学函数的显示持续时间（单位：秒）
	[SerializeField, Min(0f)]
	float functionDuration = 1f;

	// 序列化且最小值为 0 的字段，表示函数之间的过渡动画持续时间（单位：秒）
	[SerializeField, Min(0f)]
	float transitionDuration = 1f;

	// 用于存储所有点（Transform）的数组，构成一个二维网格
	Transform[] points;

	// 当前函数已持续的时间
	float duration;

	// 是否正在两个函数之间进行过渡
	bool transitioning;

	// 当前正在过渡的目标函数（即下一组函数）
	FunctionLibrary.FunctionName transitionFunction;

	// Awake 方法在脚本实例被加载时调用，通常用于初始化
	void Awake()
	{
		// 计算每个点之间的间隔，总范围是 [-1, 1]，所以步长为 2 / resolution
		float step = 2f / resolution;

		// 每个点的缩放大小，设为与步长一致，让点之间保持均匀间隔视觉效果
		var scale = Vector3.one * step;

		// 初始化点数组，总数为 resolution * resolution，即一个 resolution x resolution 的二维网格
		points = new Transform[resolution * resolution];

		// 循环创建每一个点（即网格中的每个顶点）
		for (int i = 0; i < points.Length; i++)
		{
			// 实例化一个点 Prefab，并保存到数组中
			Transform point = points[i] = Instantiate(pointPrefab);

			// 设置点的本地缩放大小
			point.localScale = scale;

			// 将点设置为当前物体的子物体，并关闭自动刷新世界坐标（提高性能）
			point.SetParent(transform, false);
		}
	}

	// 每一帧调用一次的 Unity 生命周期方法
	void Update()
	{
		// 累加当前帧的时间差到 duration，用于控制函数切换和过渡
		duration += Time.deltaTime;

		// 如果当前处于函数过渡状态
		if (transitioning)
		{
			// 如果过渡时间已超过设定的 transitionDuration，则结束过渡
			if (duration >= transitionDuration)
			{
				duration -= transitionDuration; // 保留多余时间，避免累积误差
				transitioning = false; // 结束过渡状态
			}
		}
		// 如果不在过渡中，但当前函数的显示时间已超出 functionDuration
		else if (duration >= functionDuration)
		{
			// 减去已显示的时间，保留余数（同样避免误差累积）
			duration -= functionDuration;

			// 开始进入过渡状态
			transitioning = true;

			// 记录当前函数，作为过渡的起始函数
			transitionFunction = function;

			// 选择下一个要切换的函数（根据所选的 TransitionMode）
			PickNextFunction();
		}

		// 根据当前是否在过渡中，决定更新当前函数还是过渡中的混合函数
		if (transitioning)
		{
			UpdateFunctionTransition(); // 更新过渡中的函数形态
		}
		else
		{
			UpdateFunction(); // 更新当前正式显示的函数
		}
	}

	// 选择下一个要显示的函数，根据 TransitionMode 决定是循环还是随机
	void PickNextFunction()
	{
		function = transitionMode == TransitionMode.Cycle ?
			// 如果是 Cycle 模式，获取当前函数的下一个函数（按顺序循环）
			FunctionLibrary.GetNextFunctionName(function) :
			// 如果是 Random 模式，获取一个不同于当前函数的随机函数
			FunctionLibrary.GetRandomFunctionNameOtherThan(function);
	}

	// 更新当前函数对应的点位置（无过渡，直接显示该函数）
	void UpdateFunction()
	{
		// 从 FunctionLibrary 获取当前函数对应的数学函数委托
		FunctionLibrary.Function f = FunctionLibrary.GetFunction(function);

		// 获取当前时间，用于动态函数（比如随时间变化的函数）
		float time = Time.time;

		// 计算每个点之间的步长
		float step = 2f / resolution;

		// v 用于表示 Z 轴方向的坐标，初始化为网格中心偏移
		float v = 0.5f * step - 1f;

		// 遍历所有点，为每个点计算其在函数上的位置
		for (int i = 0, x = 0, z = 0; i < points.Length; i++, x++)
		{
			// 每一行分辨率个点后，重置 x 并增加 z（即换行）
			if (x == resolution)
			{
				x = 0;
				z += 1;
				// 计算当前 Z 轴位置的偏移值
				v = (z + 0.5f) * step - 1f;
			}

			// 计算当前 X 轴位置的偏移值
			float u = (x + 0.5f) * step - 1f;

			// 通过函数 f 计算该点在三维空间中的位置，并设置其本地坐标
			points[i].localPosition = f(u, v, time);
		}
	}

	// 更新函数过渡状态下的点位置（即从上一个函数形态过渡到当前函数形态）
	void UpdateFunctionTransition()
	{
		// 获取过渡起始函数和目标函数
		FunctionLibrary.Function
			from = FunctionLibrary.GetFunction(transitionFunction), // 原函数
			to = FunctionLibrary.GetFunction(function);           // 目标函数

		// 计算当前过渡的进度（0 ~ 1）
		float progress = duration / transitionDuration;

		// 当前时间，用于动态函数
		float time = Time.time;

		// 步长和 v 值初始化（同 UpdateFunction）
		float step = 2f / resolution;
		float v = 0.5f * step - 1f;

		// 遍历所有点，计算过渡中的位置（通过 Morph 方法插值）
		for (int i = 0, x = 0, z = 0; i < points.Length; i++, x++)
		{
			if (x == resolution)
			{
				x = 0;
				z += 1;
				v = (z + 0.5f) * step - 1f;
			}

			float u = (x + 0.5f) * step - 1f;

			// 使用 FunctionLibrary.Morph 方法，对两个函数在当前 (u,v,time) 处的值进行插值，得到过渡位置
			points[i].localPosition = FunctionLibrary.Morph(
				u, v, time, from, to, progress
			);
		}
	}
}